#include "laurin-discover-network-clients-module.h"
#include "laurin-utils.h"
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <sys/time.h>
#include <sys/select.h>
#include <libnet.h>


// 2 millisecond
#define PACKET_WAIT_INTERVAL 2000


/* redefine the arp header */
typedef struct laurin_arp_hdr
{
	u_int16_t htype;		/* ARP Hardware Format */
	u_int16_t ptype;		/* ARP Protocol Format */
	u_char hlen;			/* ARP Hardware Address Length */
	u_char plen;			/* ARP Protocol Address Length */
	u_int16_t op;			/* ARP Opcode */
	u_char sha[ETH_ALEN];	/* ARP Sender Ethernet Address */
	u_char spa[IP_ALEN];	/* ARP Sender IP Address */
	u_char tha[ETH_ALEN];	/* ARP Target Ethernet Address */
	u_char tpa[IP_ALEN];	/* ARP Target IP Address */
} laurin_arp_header;






void processPacketExtForClients(u_char* arg, const struct pcap_pkthdr *header,const u_char *data)
{
	((LaurinDiscoverNetworkClientsModule*)arg)->processPacket(header, data);
}


LaurinDiscoverNetworkClientsModule::LaurinDiscoverNetworkClientsModule(const Glib::ustring& ifaceName, LaurinModuleHandler* handler): LaurinModule(Glib::ustring("LAURIN DISCOVER NETWORK CLIENTS"), handler)
{
	_device = new LaurinNetworkDevice(ifaceName);
	_errmess = _device->getErrorMessage();
	_timeout = 1; 		// 1 seconds
}


LaurinDiscoverNetworkClientsModule::~LaurinDiscoverNetworkClientsModule()
{
	delete _device;
}


bool LaurinDiscoverNetworkClientsModule::isInitialized() const
{
	return _device->isInitialized();
}


int LaurinDiscoverNetworkClientsModule::getPacketTimeout() const
{
	return _timeout;
}


void LaurinDiscoverNetworkClientsModule::setPacketTimeout(int second)
{
	_timeout = second;
}


LaurinIPNetwork* LaurinDiscoverNetworkClientsModule::getIPNetwork() const
{
	if(isInitialized())
		return _device->getIPNetwork();

	return NULL;
}


bool LaurinDiscoverNetworkClientsModule::run()
{

	LaurinIPRange* range = _device->getIPNetwork()->getIPRange();
	_device->getIPNetwork()->removeAll();
	bool cont = true;


	// apply a filter for capture only arp packet
	Glib::ustring filter = Glib::ustring("arp dst ").append(getReadableIP(_device->getIP()));
	if(!_device->setCaptureFilter(filter, _device->getIPNetwork()->getSubnetMask()))
	{
		_errmess = _device->getErrorMessage();
		return false;
	}


	// write resources
	u_int32_t ip = _device->getIP();
	u_int8_t* mac = _device->getMAC();
	u_int32_t last_ip = range->getLastIPAddress();
	libnet_t* icontext = _device->getInjectionContext();
	u_int32_t dst_ip = -1;
	libnet_ptag_t arp = 0;


 	// read resources
	pcap_t* ccontext = _device->getCaptureContext();
	int sd  = pcap_get_selectable_fd(ccontext);
	fd_set fds;

    // ?!?
    if( sd < 0)
    {
    	_errmess = Glib::ustring("Error on acquire file descriptor for ").append(_device->getDeviceName()).append(" on ").append(getModuleName()).append(" : ").append(pcap_geterr(_device->getCaptureContext()));
    	return false;
    }


    /**
     * Timeout is specified by _timeout variable.
     * if we call select() passing _timeout, the procedure is very slow because
     * for each packet it will be blocked on select() for a  "_timeout" interval.
     *
     * So we use another approach:
     *
     * 1) Check through select if data are avaibles, waiting at most PACKET_WAIT_INTERVAL
     * 2) If so, read packet
     * 3) If not and we have ip to check, send an arp request
     * 4) Otherwise wait for _timeout
     *
     * Doing so, we don't wait _timeout for each packet.
     */

    bool loop = true;
    bool activate_timeout = false;		// if all the arp request are sent, then activate the timeout with "_timeout" value.
    int rs = -1;

    while(loop)
    {
    	FD_ZERO(&fds);					// clear fd set
    	FD_SET(sd, &fds);				// add the descriptor to the set

    	struct timeval waitframe;
    	waitframe.tv_sec = 0;
    	waitframe.tv_usec = PACKET_WAIT_INTERVAL;


    	// are data avaibles?
    	if(select( sd + 1, &fds, NULL, NULL, &waitframe) > 0)
    	{
    		rs = pcap_dispatch(ccontext, 1, processPacketExtForClients, (u_char*) this);
    	}
    	else
    	{
    		// there aren't ips to check
    		if(activate_timeout)
    		{

    			waitframe.tv_usec = 0;
    			waitframe.tv_sec = _timeout;

    			if(select( sd + 1, &fds, NULL, NULL, &waitframe) > 0)
    			{
    				rs = pcap_dispatch(ccontext, 1, processPacketExtForClients, (u_char*) this);
    			}
    			else
    				loop = false;

    		}
    		else
    		{
    			// send arp request

        		dst_ip = range->getCurrentIPAddress();


        		if(arp = libnet_autobuild_arp(ARPOP_REQUEST, mac, (u_int8_t*) &ip, eth_xmas, (u_int8_t*) &dst_ip, icontext))
    			{
        			if(libnet_autobuild_ethernet(eth_xmas, ETHERTYPE_ARP, icontext))
        			{
        				// write packet
    					libnet_write(icontext);
        			}
    			}

    			if(dst_ip == last_ip)
    				activate_timeout = true;


    			range->next();
    			libnet_clear_packet(icontext);

    		}
    	}

		loop &= !isStopRequestPending() && rs != 2;
    }




    // arp reply from our station can not be sent.
    _device->getIPNetwork()->addClient(LaurinIPNetworkClient(_device->getIP(), _device->getMAC()), true);


    // it's a copy
    delete range;


    return true;
}


void LaurinDiscoverNetworkClientsModule::innerStop()
{
	pcap_breakloop(_device->getCaptureContext());
}


void LaurinDiscoverNetworkClientsModule::processPacket(const struct pcap_pkthdr *header,const u_char *data)
{

	// mac hdr + arp frame
	if(header->caplen < 42)
		return ;

	struct ether_header* eh = NULL;
	eh = (struct ether_header *) data;

	struct laurin_arp_hdr* ah = NULL;

	char* tmp = (char*)eh;
	ah = (struct laurin_arp_hdr*) & tmp[sizeof(*eh)];


	if(ntohs(ah->op) == ARPOP_REPLY && ntohs(ah->htype) == ARPHRD_ETHER && ntohs(ah->ptype) == 0x0800)
	{

		u_int32_t* t = (u_int32_t*)&ah->spa;
		LaurinIPNetworkClient client(*t, (u_int8_t*)eh->ether_shost);

		_device->getIPNetwork()->addClient(client, true);
	}

}

